﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Threading;

namespace testParallelFxInWpfApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        CancellationTokenSource tokenSource = new CancellationTokenSource();

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            // http://blogs.msdn.com/b/csharpfaq/archive/2010/07/19/parallel-programming-task-cancellation.aspx

            textBlock1.Text = "";
            label1.Content = "Milliseconds: ";

            var watch = Stopwatch.StartNew();

            #region Implementation with "Parallel.For"

            /*
            Parallel.For(2, 20, (i) =>
            {
                var result = SumRootN(i);
                //textBlock1.Text += "root " + i.ToString() + " " +
                //                    result.ToString() + Environment.NewLine;

                // Use the UI's Dispatcher to send a delegate to the UI thread
                this.Dispatcher.BeginInvoke(
                    new Action( () => textBlock1.Text += "root " + i.ToString() + " " + result.ToString() + Environment.NewLine)
                    , null);
            });
            */

            #endregion

            #region Implementation with "Dispatcher"

            /*
            List<Task> tasks = new List<Task>();

            for (int i = 2; i < 20; i++)
            {
                int j = i;
                var t = Task.Factory.StartNew(() => 
                {
                    var result = SumRootN(j);
                    this.Dispatcher.BeginInvoke(new Action(
                        () => 
                        {
                            textBlock1.Text += "root " + j.ToString() + " " + result.ToString() + Environment.NewLine;
                        }), null);
                });

                tasks.Add(t);
            }

            Task.Factory.ContinueWhenAll(tasks.ToArray(), 
                r => 
                {
                    var time = watch.ElapsedMilliseconds;
                    this.Dispatcher.BeginInvoke(new Action(() => 
                    {
                        label1.Content = time.ToString();
                    }), null);
                });
            */

            #endregion

            #region Implementation with "TaskScheduler"

            List<Task> tasks = new List<Task>();
            var ui = TaskScheduler.FromCurrentSynchronizationContext();
            for (int i = 2; i < 20; i++)
            {
                int j = i;
                var compute = Task.Factory.StartNew(() =>
                {
                    return SumRootN(j);
                }, tokenSource.Token);

                tasks.Add(compute);

                #region Using try-catch for handling AggregateException

                /*
                var display = compute.ContinueWith(resultTask =>
                    {
                        try
                        {
                            textBlock1.Text += "root " + j.ToString() + " " + 
                                compute.Result.ToString() + Environment.NewLine;
                        }
                        catch (AggregateException ae)
                        {
                            foreach (var inner in ae.InnerExceptions)
                            {
                                textBlock1.Text += "root " + j.ToString() + " " 
                                    + inner.Message + Environment.NewLine;
                            }
                        }
                    }, ui);
                */

                #endregion

                #region More elegant way of dealing with the AggregateException

                /* Method one
                var display = compute.ContinueWith(resultTask =>
                    {
                        textBlock1.Text += "root " + j.ToString() + " " +
                            compute.Result.ToString() + Environment.NewLine;
                    }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, ui);
                */

                var displayResults = compute.ContinueWith(resultTask =>
                      textBlock1.Text += "root " + j.ToString() + " " +
                                             compute.Result.ToString() +
                                             Environment.NewLine,
                          CancellationToken.None,
                          TaskContinuationOptions.OnlyOnRanToCompletion,
                          ui);

                var displayCancelledTasks = compute.ContinueWith(resultTask =>
                                                textBlock1.Text += "root " + j.ToString() +
                                                                   " canceled" +
                                                                   Environment.NewLine,
                                                CancellationToken.None,
                                                TaskContinuationOptions.OnlyOnCanceled, 
                                                ui);

                #endregion
            }

            Task.Factory.ContinueWhenAll(tasks.ToArray(),
                 result =>
                 {
                     var time = watch.ElapsedMilliseconds;
                     label1.Content += time.ToString();
                 }, CancellationToken.None, TaskContinuationOptions.None, ui);

            #endregion
        }

        private double SumRootN(int root)
        {
            double result = 0;
            for (int i = 1; i < 10000000; i++)
            {
                // The ThrowIfCancellationRequested method indicates so-called “cooperative cancellation,”
                // which means that the task throws an exception to show 
                // that it accepted the cancellation request and will stop working.

                // In this case, the thrown exception is handled by the TPL, 
                // which transitions the task to the canceled state. 
                // You cannot and should not handle this exception in your code. 
                // However, Visual Studio checks for all unhandled exceptions 
                // and shows them when in debug mode.
                tokenSource.Token.ThrowIfCancellationRequested();

                result += Math.Exp(Math.Log(i) / root);
            }

            return result;
        }

        private void button2_Click(object sender, RoutedEventArgs e)
        {
            tokenSource.Cancel();
            textBlock1.Text += "Cancel" + Environment.NewLine;
        }
    }
}
